header {

/**
 *
 * @Author: Marek Zakowicz <mazak (at) debian (dot) linux.org.pl>
 *
 * Copyright (c) 2005, 2006, 2007 Marek Zakowicz. All rights reserved.
 *
 * Licence: This code may be used, (re)distribute and/or modify under the terms one of the following two kinds of licence:
 *          1) as free software under GPL 3 (General Public Licence ver. 3 as published by the Free Software Foundation)
 *          2) as software under the terms individual licence from Marek Zakowicz
 *
 *          This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *          without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

package jsp2php;
import java.io.*;
import java.util.*;
import static jsp2php.DeclarationAction.*;
import static jsp2php.StatementType.*;
}

class JavaFragmentParser extends Parser;

options {
   k=2;
}

{
   JavaFragmentTranslator jbt;
   JSPFTranslator      tr;
}

// SYNDIAGSTART

fragment returns [Translation tr=null]
   : (statement)*;

statement { Translation t; jbt.printWS=false; jbt.controlStarted=false;}
   : (IDENTIFIER IDENTIFIER) => varDeclaration[true] semicolon
   | (IDENTIFIER COLON) => labeledStatement
   | {jbt.printWS=true; } lbracket { jbt.blockBegin(null, BLOCK); }
   | {jbt.printWS=true; } rbracket { jbt.blockEnd(); }
   | ( {jbt.statementBegin( null, EXPR, false ); } t = expression2 { jbt.print( t.text ); } )? semicolon { jbt.printWS=true; jbt.print(""); jbt.statementEnd(); }
   | ifStatement
   | elseStatement
   | switchStatement
   | { jbt.printWS=true; } caseStatement
   | { jbt.printWS=true; } "default" { jbt.print( "default" ); } colon
   | doStatement
   | whileStatement
   | forStatement
   | breakStatement
   | continueStatement
   ;

labeledStatement { String label; }
   : (IDENTIFIER COLON LBRACKET) =>
     label = identifier COLON { jbt.print("do /* "+ label +" : */ "); jbt.printWS=true; } lbracket { jbt.blockBegin(label, BLOCK); }
   | label = identifier COLON { jbt.print("/* "+ label +" : */"); jbt.printWS=true; jbt.statementBegin( label, LABEL, false ); }
     ( whileStatement | forStatement | doStatement )
   //| label = identifier COLON { System.err.println("Statement labeled with "+ label +" hasn't bracket!  Label ignored!"); }
      // TODO: Gdybym mial kompletne, javowe wyrazenia (if-else, switch, while, for) to moglbym to dolozyc ich obsluge.
      //       Jesli kiedys napotkam problem z etykietami bez nawiasow, to warto zaimplementowac kompletne, javowe wyrazenia tylko dla takiej sytuacji.
      //       A moze kompromisem jest obsluzenie wyrazen typu "l: for(;;) {" przez pominiecie dodatkowej petli oraz typu "if() {" z dodatkowa petla? (kompromis polega na wymaganiu lewego nawiasu otwierajacego...
   ;

breakStatement { String label=null; }
   : ("break" identifier) => "break" label = identifier { jbt.breakContinueStatement("break", label); jbt.printWS=true; } semicolon { jbt.statementEnd(); }
   | "break" { jbt.breakContinueStatement("break", null); jbt.printWS=true; } semicolon { jbt.statementEnd(); }
   ;

continueStatement { String label=null; }
   : ("continue" identifier) => "continue" label = identifier { jbt.breakContinueStatement("continue", label); jbt.printWS=true; } semicolon { jbt.statementEnd(); }
   | "continue" { jbt.breakContinueStatement("continue", null); jbt.printWS=true; } semicolon { jbt.statementEnd(); }
   ;

// TODO: na antlr.org jest opisany gunit - mi bylo bardzo wygodnie testowac calosc, ale moze w jakims momencie projektu (po zamrozeniu gramatyki) tak szczegolowe testy maja tez sens.  Tak czy siak - warto sie do tego odniesc w pracy i bibliografii
switchStatement { Translation t; } : switchID lparen t = expression2 { jbt.print( t.text ); } rparen lbracket { jbt.blockBegin(null, SWITCH); } ;

caseStatement { Translation t; } : caseID t = expression2 { jbt.print( t.text ); } colon;

ifStatement    { Translation t=null; } : ifID lparen t = expression2 { jbt.print( t.text ); } rparen { jbt.controlStarted = true; jbt.statementBegin( null, IF, false );  } ;

elseStatement  { jbt.printWS=true; }   : elseID { jbt.controlStarted = true; jbt.statementBegin( null, ELSE, false ); } ;

doStatement { jbt.printWS=true; }      : doID { jbt.statementBegin( null, LOOP, false ); } ;

whileStatement { Translation t=null; jbt.printWS=true; } : whileID lparen t = expression2 { jbt.print( t.text ); } rparen { jbt.controlStarted = true; jbt.statementBegin( null, LOOP, false ); } ;

forStatement { Translation t=null; }
   :
   forID lparen (forInit)? { jbt.printWS=true; } semicolon ( t = expression2 { jbt.print( t.text ); } )? semicolon (expressionList)? rparen
   { jbt.controlStarted = true; jbt.statementBegin( null, LOOP, false ); }
   ;

forInit : ( (IDENTIFIER IDENTIFIER) => varDeclaration[false] | expressionList ) ;

expressionList { Translation t=null; } : t = expression2 { jbt.print( t.text ); } ( comma [false] t = expression2 { jbt.print( t.text ); } )* ;

varDeclaration [boolean comma2semicolon]
   {
      String type;
      jbt.printWS=false;
   }
   : type=identifier variable[type] ( comma [comma2semicolon] variable[type] )*
     {
         jbt.wsCache.setLength(0);
         jbt.printWS=true;
      }
   ;

expression1 returns [Translation expr = null] : { jbt.printWS=false; } expr = expression {
                                                                                 expr.text+=jbt.wsCache.toString().trim();
                                                                                 jbt.wsCache.setLength(0);
                                                                                 jbt.printWS=true;
                                                                              };


expression2 returns [Translation expr = null] { boolean pws = jbt.printWS; } : //{ jbt.printWS=false; } expr = expression { if(pws) {jbt.wsCache.setLength(0); jbt.printWS=pws;} };
{ jbt.printWS=false; } expr = expression { expr.text+=jbt.wsCache.toString().trim(); jbt.wsCache.setLength(0); jbt.printWS=true; };

expression returns [Translation expr = null]
   : expr = assign_expr_left
   ;

assign_expr_left returns [Translation result = null] { Translation right = null; }
   : result = question_expr ( result = assign_expr_right[result] )?
   ;

assign_expr_right [Translation left] returns [Translation result = null]
   : EQ         result = expression {
      Set<String> modifiedVariables = new HashSet<String>( result.modifiedVariables );
      modifiedVariables.add(left.text);
      result = new Translation(left.text +" = "+   result.text, left.type, false, left.not_null && result.not_null, modifiedVariables);
     }
   | PLUS_EQ    result = expression { result = tr.add_eq(left, result); }
   | MINUS_EQ   result = expression { result = tr.op_eq("-", left, result); }
   | STAR_EQ    result = expression { result = tr.op_eq("*", left, result); }
   | SLASH_EQ   result = expression { result = tr.div_eq(left, result); }
   | PERCENT_EQ result = expression { result = tr.op_eq("%", left, result); }
   | LSH_EQ     result = expression { result = tr.op_eq("<<", left, result); }
   | RSH_EQ     result = expression { result = tr.op_eq(">>", left, result); }
   | URSH_EQ    result = expression {
       AddOns.use("j2p_ursh");
       result = new Translation(left.text +" = j2p_ursh("+ left.text +", "+ result.text +")", JavaType.integer(), false, left.not_null && result.not_null);
     }
   | AND_EQ     result = expression { result = tr.op_eq("&", left, result); }
   | POWER_EQ   result = expression { result = tr.op_eq("^", left, result); }
   | PIPE_EQ    result = expression { result = tr.op_eq("|", left, result); }
   /*
   | MINUS_EQ   result = expression { result = new Translation(left.text +" -= "+  result.text, left.type, false, left.not_null && result.not_null); }
   | STAR_EQ    result = expression { result = new Translation(left.text +" *= "+  result.text, left.type, false, left.not_null && result.not_null); }
   | PERCENT_EQ result = expression { result = new Translation(left.text +" %= "+  result.text, left.type, false, left.not_null && result.not_null); }
   | LSH_EQ     result = expression { result = new Translation(left.text +" <<= "+ result.text, left.type, false, left.not_null && result.not_null); }
   | RSH_EQ     result = expression { result = new Translation(left.text +" >>= "+ result.text, left.type, false, left.not_null && result.not_null); }
   | AND_EQ     result = expression { result = new Translation(left.text +" &= "+  result.text, left.type, false, left.not_null && result.not_null); }
   | POWER_EQ   result = expression { result = new Translation(left.text +" ^= "+  result.text, left.type, false, left.not_null && result.not_null); }
   | PIPE_EQ    result = expression { result = new Translation(left.text +" |= "+  result.text, left.type, false, left.not_null && result.not_null); }
   */
   ;

question_expr returns [Translation result = null] { Translation first = null, second = null; }
   : result = cond_or_expr
     ( QUESTION first = assign_expr_left COLON second = question_expr { result = tr.question(result, first, second); } )?
       //{ result = new Translation(result.text +" ? "+ first.paren() +" : "+ second.paren(), first.type, false, first.not_null && second.not_null); } )?
   ;

cond_or_expr returns [Translation result = null] { Translation right = null; }
   : result = cond_and_expr
     ( PIPE_PIPE right = cond_and_expr { result = new Translation(result.text +" || "+ right.text, result.type, false, result.not_null && right.not_null); } )*
   ;

cond_and_expr returns [Translation result = null] { Translation right = null; }
   : result = or_expr
     ( AND_AND right = or_expr { result = new Translation(result.text +" && "+ right.text, result.type, false, result.not_null && right.not_null); } )*
   ;

or_expr returns [Translation result = null] { Translation right = null; }
   : result = xor_expr
     ( PIPE right = xor_expr { result = tr.bit('|', result, right); } )*
   ;

xor_expr returns [Translation result = null] { Translation right = null; }
   : result = and_expr
     ( POWER right = and_expr { result = tr.bit('^', result, right); } )*
   ;

and_expr returns [Translation result = null] { Translation right = null; }
   : result = eq_expr_left
     ( AND right = eq_expr_left { result = tr.bit('&', result, right); } )*
   ;

eq_expr_left returns [Translation result = null]
   : result = rel_expr_left ( result = eq_expr_right[result] )*
   ;

eq_expr_right [Translation left] returns [Translation result = null]
   : EQEQ result = rel_expr_left { result = new Translation(left.paren() + " == " + result.paren(), JavaType.bool(), false, left.not_null && result.not_null); }
   | NEQ result = rel_expr_left { result = new Translation(left.paren() + " != " + result.paren(), JavaType.bool(), false, left.not_null && result.not_null); }
   ;

rel_expr_left returns [Translation result = null]
   : result = shift_expr_left ( result = rel_expr_right[result] )?
   ;

rel_expr_right [Translation left] returns [Translation result = null]
   : LT result = shift_expr_left { result = new Translation(left.text + " < " + result.text, JavaType.bool(), false, left.not_null && result.not_null); }
   | LE result = shift_expr_left { result = new Translation(left.text + " <= " + result.text, JavaType.bool(), false, left.not_null && result.not_null); }
   | GT result = shift_expr_left { result = new Translation(left.text + " > " + result.text, JavaType.bool(), false, left.not_null && result.not_null); }
   | GE result = shift_expr_left { result = new Translation(left.text + " >= " + result.text, JavaType.bool(), false, left.not_null && result.not_null); }
   ;

shift_expr_left returns [Translation result = null]
   : result = additive_expr_left ( result = shift_expr_right[result] )*
   ;

shift_expr_right [Translation left] returns [Translation result = null]
   : LSH result = additive_expr_left { result = new Translation(left.text + " << " + result.text, JavaType.integer(), false, left.not_null && result.not_null); }
   | RSH result = additive_expr_left { result = new Translation(left.text + " >> " + result.text, JavaType.integer(), false, left.not_null && result.not_null); }
   | URSH result = additive_expr_left {
      AddOns.use("j2p_ursh");
      result = new Translation("j2p_ursh("+ left.text +", "+ result.text +")", JavaType.integer(), false, left.not_null && result.not_null);
     }
   ;

additive_expr_left returns [Translation result = null]
   : result = multiplicative_expr_left ( result = additive_expr_right[result] )*
   ;

additive_expr_right [Translation left] returns [Translation result = null]
   : PLUS result = multiplicative_expr_left { result = tr.add(left, result); }
   | MINUS result = multiplicative_expr_left
     { result = new Translation(left.text + " - " + result.text, TypeCalculator.add_type(left.type, result.type), false, left.not_null && result.not_null); }
   ;

multiplicative_expr_left returns [Translation result = null]
   : result = unary_expr ( result = multiplicative_expr_right[result] )*
   ;

multiplicative_expr_right [Translation left] returns [Translation result = null]
   : STAR  result = unary_expr
     { result = new Translation(left.text + " * " + result.text, TypeCalculator.mul_type(left.type, result.type), false, left.not_null && result.not_null); }
   | PERCENT result = unary_expr
     { result = new Translation(left.text + " % " + result.text, TypeCalculator.mul_type(left.type, result.type), false, left.not_null && result.not_null); }
   | SLASH result = unary_expr { result = tr.div(left, result); }
   ;

unary_expr returns [Translation result = null]
   { String type_id; }
   : ( LPAREN identifier RPAREN unary_expr ) => LPAREN type_id = identifier RPAREN result = unary_expr
     {
      JavaType target_type = JavaType.value_type(type_id);
      if( result.consistent ) {
         result = new Translation("("+ target_type.translator().php_name() +") " + result.text, target_type, false, result.not_null);
      }
      else {
         result = new Translation("("+ target_type.translator().php_name() +") (" + result.text + ")", target_type, false, result.not_null);
      }
     }
   | MINUS       result = unary_expr { result = tr.unary_arithmetic('-', result); }
   | PLUS        result = unary_expr { result = tr.unary_arithmetic('+', result); }
   | TILDE       result = unary_expr { result = tr.unary_arithmetic('~', result); }
   | EXCLAMATION result = unary_expr { result = new Translation("! " + result.text, result.type, false, result.not_null); }
   | result = unary_prefix_expr
   ;

unary_prefix_expr returns [Translation result = null]
   : (dot_expr_left DEC) => result = dot_expr_left DEC { result = new Translation(result.text + "--", result.type, false, result.not_null); }
   |(dot_expr_left INC) => result = dot_expr_left INC { result = new Translation(result.text + "++", result.type, false, result.not_null); }
   | result = dot_expr_left
   | DEC result = dot_expr_left { result = new Translation("--" + result.text, result.type, false, result.not_null); }
   | INC result = dot_expr_left { result = new Translation("++" + result.text, result.type, false, result.not_null); }
   ;

dot_expr_left returns [Translation result = null]
   : result = simple_expr[null] ( result = dot_expr_right[result] )*
   ;

dot_expr_right [Translation left] returns [Translation result = null]
   { String s = null; }
   : (DOT identifier LPAREN) => DOT result = call_expr[left]
   //| DOT s = identifier { result = left.type.translator().field(s); }
   | DOT result = field_expr[left]
   ;

simple_expr [Translation left] returns [Translation expr = null]
   : ( b_literal ) => expr = b_literal // TODO: check left==null
   | expr = d_literal // TODO: check left==null
   | expr = i_literal // TODO: check left==null
   | expr = s_literal // TODO: check left==null
   | LPAREN expr = expression RPAREN { expr.text = "( "+ expr.text +" )"; expr.consistent = true; }
   | (identifier LPAREN) => expr = call_expr[left] // przy translacji metody serwletu, ale tego jeszcze nie wspieram
   | expr = field_expr[left]
   ;

call_expr [Translation left] returns [Translation result = null]
   { String s; ArrayList<Translation> args = new ArrayList<Translation>(); Translation arg = null; }
   : s = identifier
     LPAREN { args.add(left); } ( arg = expression { args.add(arg); } (COMMA arg = expression { args.add(arg); } )*  )? RPAREN
     { result = left.type.translator().call(s, args.toArray( Translation.eta() ) ); }
   ;

field_expr [Translation left] returns [Translation result = null] { String s = null; }
   : s = identifier {
         if(left == null) {
            result = tr.field(s);
         }
         else {
            result = left.type.translator().field(s);
         }
      }
   ;

variable [String var_type]
   {
      String var_id = null;
      Translation expr=null;
   }
   : (identifier EQ expression) =>
      var_id = identifier
      {
         jbt.print( tr.declare(var_type, var_id, DA_COMMENT_TYPE_ONLY).text );
      }
      EQ expr = expression
      {
         jbt.print( tr.assign(var_type, var_id, expr).text );
      }
   | var_id = identifier { jbt.print( tr.declare(var_type, var_id, DA_COMMENT).text ); }
   ;

// SYNDIAGSTOP

i_literal returns [Translation tn = null] : t: INTEGER { tn = JavaType.value_translator("int").literal( t.getText() ); };
d_literal returns [Translation tn = null] : t: DOUBLE { tn = JavaType.value_translator("double").literal( t.getText() ); };
s_literal returns [Translation tn = null] : t: STRING { tn = JavaType.value_translator("String").literal( t.getText() ); };
b_literal returns [Translation tn = null]
   : "true"  { tn = JavaType.value_translator("boolean").literal( "true" ); }
   | "false" { tn = JavaType.value_translator("boolean").literal( "false" ); }
   ;
identifier returns [String s = null] : t: IDENTIFIER { s = t.getText(); };

string      : t: STRING     { jbt.print(t.getText());       };
number      : t: NUMBER     { jbt.print(t.getText());       };
character   : t: CHARACTER  { jbt.print(t.getText());       }; // TODO: Co w php z tym mozna zrobic (unicode?)?  Jest tez kwestia typu wyr.

lparen      :    LPAREN     { jbt.print("(");               };
rparen      :    RPAREN     { jbt.print(")");               };
lbracket    :    LBRACKET   { jbt.print("{");               };
rbracket    :    RBRACKET   { jbt.print("}");               };
semicolon   :    SEMICOLON  { jbt.print(";");               };
colon       :    COLON      { jbt.print(":");               };
comma[boolean comma2semicolon] :  COMMA { if(comma2semicolon) jbt.print("; "); else jbt.print(","); };
returnID    :    "return"   { jbt.print("return");          };
ifID        :    "if"       { jbt.print("if");              };
elseID      :    "else"     { jbt.print("else");            };
doID        :    "do"       { jbt.print("do");              };
whileID     :    "while"    { jbt.print("while");           };
forID       :    "for"      { jbt.print("for");             };
switchID    :    "switch"   { jbt.print("switch");          };
caseID      :    "case"     { jbt.print("case ");           };

class JavaFragmentLexer extends Lexer;

options {
   k=4; // Length champion is unsigned right shift assignment >>>=
   charVocabulary = '\0'..'\377';
}

{
   JavaFragmentTranslator jbt;

   public void uponEOF() /*throws TokenStreamException, CharStreamException*/ {
   }
}

// TODO: Przeplatajace sie komentarze (brak wsparcia z uwagi na skomplikowana (?) budowe leksera) + opis w mgr
// TODO: Numerowanie linii + opis w mgr

protected WS1         : ('\0'..'\40')+ ;
WS                    : t: WS1 { jbt.printWS( t.getText() ); $setType(Token.SKIP); } ;

protected SL_COMMENT1 : "//" ( ~( '\n' | '\r' ) )* ( '\n' | '\r' ('\n')? | { LA(1)==EOF_CHAR }? ) ;
SL_COMMENT            : t: SL_COMMENT1 { jbt.printWS( t.getText() ); $setType(Token.SKIP); };

// TODO: Do obslugi przeplatajacych sie z z <%[=] %> komentarzy nadaje sie nastepujacy ML_COMMENT1
// protected ML_COMMENT1 : "/*" ( options { generateAmbigWarnings=false; } : { LA(2)!='/' }? '*' | '\r' '\n' | '\r' | '\n' | ~('*'|'\n'|'\r') )* ( "*/" | { LA(1)==EOF_CHAR }? ) ;
// ja jednak uproszcze sobie zycie na tym etapie i wyklucze ten sposob komentowania
protected ML_COMMENT1 : "/*" ( options { generateAmbigWarnings=false; } : { LA(2)!='/' }? '*' | '\r' '\n' | '\r' | '\n' | ~('*'|'\n'|'\r') )* "*/" ;
ML_COMMENT            : t: ML_COMMENT1 { jbt.printWS( t.getText() ); $setType(Token.SKIP); };

protected CHAR        : ( ~( '=' | '(' | ')' | '\\' | '\'' | '"' | '\0'..'\40' ) ); //TODO: Z tego mozna jeszcze wyodrebnic slashe z uwagi na komentarze
protected DIGIT       : ('0'..'9');
protected HEXDIGIT    : ('0'..'9'|'a'..'f'|'A'..'F') ;
protected LETTER      : ('A'..'Z'|'_'|'a'..'z');
IDENTIFIER            : LETTER (LETTER | DIGIT)*;
// TODO: liczby osemkowe sa troche nadmiarowe - zakladam jednak, ze translator ma tlumaczyc poprawny kod w javie, a nie go weryfikowac; nie ma ryzyka niejednoznacznosci.
protected ESCAPE      : '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
                      | '\\' ('0'..'7') (options { warnWhenFollowAmbig = false; } : '0'..'7')? (options { warnWhenFollowAmbig = false; } :  '0'..'7')?
                      | '\\' 'u' HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT;

/*
protected HEX         : '0' ('x'|'X') (HEXDIGIT)+  ;
protected OCTAL       : '0' ('0'..'7')+;
protected DECIMAL     : ('0' | ('1'..'9' ('0'..'9')*) );
INTEGER               : (HEX | OCTAL | DECIMAL) ('l'|'L')?;
*/
//INTEGER               : DIGIT ('x'|'X')? (HEXDIGIT)* ('l'|'L')?; // TODO: to jest mocno nadmiarowe, ale ma zastosowanie uwaga dot. ESCAPE
// TODO: liczby calkowite sa mocno nadmiarowe, ale ma zastosowanie uwaga dot. ESCAPE
//INTEGER               : (options { warnWhenFollowAmbig = false; } : DIGIT) ('x'|'X')? (HEXDIGIT)* ('l'|'L')?;

protected DOUBLE_SUF1 : ('e'|'E') ('+'|'-')? (DIGIT)+;
protected DOUBLE_SUF2 : 'f' | 'F' | 'd' | 'D' ;
INTEGER               : (DIGIT)+ (DOT (DIGIT)* {$setType(DOUBLE);} )? (DOUBLE_SUF1 {$setType(DOUBLE);} )? (DOUBLE_SUF2 {$setType(DOUBLE);} )? ;
DOUBLE                : DOT (DIGIT)+ (DOUBLE_SUF1)? (DOUBLE_SUF2)?;


STRING                : '"'  ( ESCAPE | ~('\\'|'"') )* '"'  ;
CHARACTER             : '\'' ( ESCAPE | ~('\\'|'"') )  '\'' ;
EQ                    : '=';
PLUS_EQ               : '+' '=';
MINUS_EQ              : '-' '=';
STAR_EQ               : '*' '=';
SLASH_EQ              : '/' '=';
PERCENT_EQ            : '%' '=';
LSH_EQ                : '<' '<' '=';
RSH_EQ                : '>' '>' '=';
URSH_EQ               : '>' '>' '>' '=';
AND_EQ                : '&' '=';
POWER_EQ              : '^' '=';
PIPE_EQ               : '|' '=';
EQEQ                  : '=' '=';
NEQ                   : '!' '=';
LT                    : '<';
GT                    : '>';
LE                    : '<' '=';
GE                    : '>' '=';
LSH                   : '<' '<';
RSH                   : '>' '>';
URSH                  : '>' '>' '>';
DOT                   : '.';
LPAREN                : '(';
RPAREN                : ')';
LBRACKET              : '{';
RBRACKET              : '}';
LSQUARE               : '[';
RSQUARE               : ']';
COMMA                 : ',';
COLON                 : ':';
SEMICOLON             : ';';
PLUS                  : '+';
INC                   : '+' '+';
MINUS                 : '-';
DEC                   : '-' '-';
STAR                  : '*';
SLASH                 : '/';
PERCENT               : '%';
TILDE                 : '~';
EXCLAMATION           : '!';
AND                   : '&';
AND_AND               : '&' '&';
POWER                 : '^';
PIPE                  : '|';
PIPE_PIPE             : '|' '|';
QUESTION              : '?';

// Ponizej jest niedeterminizm wynikajacy z tego, ze slowa kluczowe nie moga byc identyfikatorami...
/*
IF                    : (options { warnWhenFollowAmbig = false; } : "if"    );
ELSE                  : (options { warnWhenFollowAmbig = false; } : "else"  );
DO                    : (options { warnWhenFollowAmbig = false; } : "do"    );
WHILE                 : (options { warnWhenFollowAmbig = false; } : "while" );
*/

// TODO: Poprawne numerowanie linii (w polaczeniu z gramatyka jsp)
// TODO: Obsluga wyjatkow bardziej przyjazna dla uzytkownika
// TODO: Konfiguracja ostrzezen i bledow (moze byc systemowa)
// TODO: Obsluga klas i tagow

